package httpclient

import (
	"bytes"
	"context"
	"encoding/json"
	"encoding/xml"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"gitee.com/git-lz/twelve/common/consts"
	"gitee.com/git-lz/twelve/common/xtrace"
	"github.com/gin-gonic/gin"
)

type Client struct {

	// 超时时间
	timeOut time.Duration

	// 最大重试次数
	maxRetry int

	// HTTP请求中的header信息
	header *sync.Map

	// HTTP请求中,携带的cookies
	cookies []*http.Cookie

	// 发起请求的client(go 自带的client)
	client *http.Client

	// 临时header,每次请求后会重置
	_header map[string]string

	// 临时cookie,每次请求后会重置
	_cookies []*http.Cookie

	// 处理HTTP返回的response
	buildResponse BuildResponse
}

// Header 为这次请求设置header,只有这次会生效
func (c *Client) Header(header map[string]string) *Client {
	c._header = header
	return c
}

// Cookies 为这次请求设置cookie,只有这次会生效
func (c *Client) Cookies(cookies []*http.Cookie) *Client {
	c._cookies = cookies
	return c
}

// AddHeader 为client添加header,原来的会保留,整个生命周期有效
func (c *Client) AddHeader(header *sync.Map) {
	header.Range(func(key, value interface{}) bool {
		c.header.Store(key, value)
		return true
	})
}

// SetHeader 为client设置新的header,原来的会删除,整个生命周期有效
func (c *Client) SetHeader(header *sync.Map) {
	c.header = header
}

// AddCookies 为client添加cookie,原来的会保留,整个生命周期有效
func (c *Client) AddCookies(cookies []*http.Cookie) {
	for _, cookie := range cookies {
		c.cookies = append(c.cookies, cookie)
	}
}

// SetCookies 为client设置新的cookie,原来的会删除,整个生命周期有效
func (c *Client) SetCookies(cookies []*http.Cookie) {
	c.cookies = cookies
}

// SetTraceId 设置trace_id
func (c *Client) SetTraceId(ctx context.Context) *Client {
	var traceHeader = &sync.Map{}
	traceHeader.Store(consts.HeaderRidKey, xtrace.TraceIdFromContext(ctx))

	if c.header == nil {
		c.header = traceHeader
	} else {
		c.AddHeader(traceHeader)
	}

	return c
}

// DoRequest 发起HTTP请求
func (c *Client) DoRequest(ctx context.Context, r *http.Request) (*http.Response, error) {
	ctx, _ = context.WithTimeout(ctx, c.timeOut)

	var (
		i    = 0
		err  error
		resp = &http.Response{}
	)

	var originalBody []byte
	if r != nil && r.Body != nil {
		originalBody, err = copyBody(r.Body)
		if err != nil {
			return nil, err
		}

		resetBody(r, originalBody)
	}

	for {
		resp, err = c.client.Do(withTrace(ctx, r))
		if err == nil {
			return resp, nil
		}

		i++
		if i > c.maxRetry {
			return resp, err
		}

		// 如果正在重试，那么释放fd
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}

		// 重置body
		if r.Body != nil {
			resetBody(r, originalBody)
		}

		time.Sleep(time.Second)
	}
}

func copyBody(src io.ReadCloser) ([]byte, error) {
	b, err := ioutil.ReadAll(src)
	if err != nil {
		return nil, err
	}
	src.Close()
	return b, nil
}

func resetBody(request *http.Request, originalBody []byte) {
	request.Body = io.NopCloser(bytes.NewBuffer(originalBody))

	request.GetBody = func() (io.ReadCloser, error) {
		return io.NopCloser(bytes.NewBuffer(originalBody)), nil
	}
}

func withTrace(ctx context.Context, req *http.Request) *http.Request {
	if ctx == nil {
		return req
	}

	if ginCtx, ok := ctx.(*gin.Context); ok {
		ctx = ginCtx.Request.Context()
	}

	return req.WithContext(ctx)
}

// SendWithMethod 指定请求的方法,发送请求
// `req` 参数 可以处理这次请求的request
func (c *Client) SendWithMethod(ctx context.Context, url, method string, body io.Reader, req func(request *http.Request)) IResponse {
	request, err := c.getRequest(method, url, body)
	if err != nil {
		return c.buildResponse(nil, err)
	}
	if req != nil {
		req(request)
	}

	return c.buildResponse(c.DoRequest(ctx, request))
}

// SendWithMethodCallBack 使用异步回调的方式,指定请求的方法,发送请求
// `req` 参数 可以处理这次请求的request
// `call` 参数,请求成功后的回调函数
func (c *Client) SendWithMethodCallBack(ctx context.Context, url, method string, body io.Reader, req func(request *http.Request), call func(response IResponse)) error {
	request, err := c.getRequest(method, url, body)
	if err != nil {
		return err
	}
	if req != nil {
		req(request)
	}
	go func() {
		call(c.buildResponse(c.DoRequest(ctx, request)))
	}()
	return nil
}

// Get 发起GET 请求
func (c *Client) Get(ctx context.Context, url string) IResponse {
	return c.SendWithMethod(ctx, url, http.MethodGet, nil, nil)
}

// GetWithBody 发起GET 请求
func (c *Client) GetWithBody(ctx context.Context, url string, value interface{}) IResponse {
	if value == nil {
		return c.buildResponse(nil, errors.New("json value is nil"))
	}
	by, err := json.Marshal(value)
	if err != nil {
		return c.buildResponse(nil, err)
	}
	reader := bytes.NewReader(by)
	return c.SendWithMethod(ctx, url, http.MethodGet, reader, EasyGetJsonRequest)
}

// GetAsync 异步请求,使用回调函数
func (c *Client) GetAsync(ctx context.Context, url string, call func(response IResponse)) error {
	return c.SendWithMethodCallBack(ctx, url, http.MethodGet, nil, nil, call)
}

// GetAsyncWithCallback 异步请求,使用接口回调
func (c *Client) GetAsyncWithCallback(ctx context.Context, url string, call ICallBack) error {
	return c.GetAsync(ctx, url, call.EasyResponseCallback)
}

// PostForm 的form请求
func (c *Client) PostForm(ctx context.Context, url string, values url.Values) IResponse {
	var reader io.Reader
	if values != nil {
		reader = strings.NewReader(values.Encode())
	}
	return c.SendWithMethod(ctx, url, http.MethodPost, reader, EasyPostFromRequest)
}

// PostFormAsync Post form 异步请求,使用回调函数
func (c *Client) PostFormAsync(ctx context.Context, url string, values url.Values, call func(response IResponse)) error {
	if call == nil {
		return errors.New("callback function is nil")
	}
	if values == nil {
		return errors.New("values is nil")
	}
	reader := strings.NewReader(values.Encode())
	return c.SendWithMethodCallBack(ctx, url, http.MethodPost, reader, EasyPostFromRequest, call)
}

// PostFormAsyncWithCallback Post form 异步请求,使用接口回调
func (c *Client) PostFormAsyncWithCallback(ctx context.Context, url string, values url.Values, call ICallBack) error {
	return c.PostFormAsync(ctx, url, values, call.EasyResponseCallback)
}

// PostBytes post 的bytes请求
func (c *Client) PostBytes(ctx context.Context, url string, value []byte, req func(request *http.Request)) IResponse {
	if value == nil {
		return c.buildResponse(nil, errors.New("PostBytes value is nil"))
	}
	reader := bytes.NewReader(value)
	return c.SendWithMethod(ctx, url, http.MethodPost, reader, req)
}

// PostBytesAsync post 的bytes请求
func (c *Client) PostBytesAsync(ctx context.Context, url string, value []byte, req func(request *http.Request), call func(response IResponse)) error {
	if call == nil {
		return errors.New("callback function is nil")
	}
	if value == nil {
		return errors.New("value is nil")
	}
	reader := bytes.NewReader(value)
	return c.SendWithMethodCallBack(ctx, url, http.MethodPost, reader, req, call)
}

// PostJson post 的json请求
func (c *Client) PostJson(ctx context.Context, url string, value interface{}) IResponse {
	if value == nil {
		return c.buildResponse(nil, errors.New("PostJson value is nil"))
	}
	by, err := json.Marshal(value)
	if err != nil {
		return c.buildResponse(nil, err)
	}
	return c.PostBytes(ctx, url, by, EasyPostJsonRequest)
}

// PostJsonAsync Post json 异步请求,使用回调函数
func (c *Client) PostJsonAsync(ctx context.Context, url string, value interface{}, call func(response IResponse)) error {
	if call == nil {
		return errors.New("callback function is nil")
	}
	if value == nil {
		return errors.New("value is nil")
	}
	by, err := json.Marshal(value)
	if err != nil {
		return errors.New("value json encode error: " + err.Error())
	}
	return c.PostBytesAsync(ctx, url, by, EasyPostJsonRequest, call)
}

// PostJsonAsyncWithCallback Post json 异步请求,使用接口回调
func (c *Client) PostJsonAsyncWithCallback(ctx context.Context, url string, values interface{}, call ICallBack) error {
	return c.PostJsonAsync(ctx, url, values, call.EasyResponseCallback)
}

// PostXml post 的xml请求
func (c *Client) PostXml(ctx context.Context, url string, value interface{}) IResponse {
	if value == nil {
		return c.buildResponse(nil, errors.New("PostJson value is nil"))
	}
	by, err := xml.Marshal(value)
	if err != nil {
		return c.buildResponse(nil, err)
	}
	return c.PostBytes(ctx, url, by, EasyPostXmlRequest)
}

// PostXmlAsync Post xml 异步请求,使用回调函数
func (c *Client) PostXmlAsync(ctx context.Context, url string, value interface{}, call func(response IResponse)) error {
	if call == nil {
		return errors.New("callback function is nil")
	}
	if value == nil {
		return errors.New("value is nil")
	}
	by, err := json.Marshal(value)
	if err != nil {
		return errors.New("value json encode error: " + err.Error())
	}
	return c.PostBytesAsync(ctx, url, by, EasyPostXmlRequest, call)
}

// PostXmlAsyncWithCallback Post xml 异步请求,使用接口回调
func (c *Client) PostXmlAsyncWithCallback(ctx context.Context, url string, values interface{}, call ICallBack) error {
	return c.PostXmlAsync(ctx, url, values, call.EasyResponseCallback)
}

// PostMultipart post 的multipart请求
func (c *Client) PostMultipart(ctx context.Context, url string, body IMultipart) IResponse {
	return c.SendWithMethod(ctx, url, http.MethodPost, body.GetBytesReader(), func(request *http.Request) {
		request.Header.Set("Content-Type", body.ContentType())
	})
}

// PostMultipartAsync post 的multipart请求,使用回调函数
func (c *Client) PostMultipartAsync(ctx context.Context, url string, body IMultipart, call func(response IResponse)) error {
	if call == nil {
		return errors.New("callback function is nil")
	}
	return c.SendWithMethodCallBack(ctx, url, http.MethodPost, body.GetBytesReader(), func(request *http.Request) {
		request.Header.Set("Content-Type", body.ContentType())
	}, call)
}

// PostMultipartAsyncWithCallback post 的multipart请求,使用接口回调
func (c *Client) PostMultipartAsyncWithCallback(ctx context.Context, url string, body IMultipart, call ICallBack) error {
	return c.PostMultipartAsync(ctx, url, body, call.EasyResponseCallback)
}

func (c *Client) PutJson(ctx context.Context, url string, value interface{}) IResponse {
	if value == nil {
		return c.buildResponse(nil, errors.New("put value is nil"))
	}
	by, err := json.Marshal(value)
	if err != nil {
		return c.buildResponse(nil, err)
	}
	reader := bytes.NewReader(by)
	return c.SendWithMethod(ctx, url, http.MethodPut, reader, func(request *http.Request) {
		request.Header.Set("Content-Type", HttpContentTypeJson)
	})
}

func (c *Client) Delete(ctx context.Context, url string) IResponse {
	return c.SendWithMethod(ctx, url, http.MethodDelete, nil, func(request *http.Request) {
		request.Header.Set("Content-Type", HttpContentTypeJson)
	})
}

// 初始化一个 http.Request, 并填充属性
func (c *Client) getRequest(method, url string, body io.Reader) (*http.Request, error) {
	request, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	if c.header != nil {
		c.header.Range(func(key, value interface{}) bool {
			k, ok1 := key.(string)
			v, ok2 := value.(string)
			if ok1 && ok2 {
				request.Header.Set(k, v)
			}
			return true
		})
	}

	for k, v := range c._header {
		request.Header.Set(k, v)
	}
	c._header = nil

	if _, e := request.Header["User-Agent"]; !e {
		request.Header.Set("User-Agent", HttpUserAgentChromePc)
	}

	for _, v := range c.cookies {
		request.AddCookie(v)
	}
	for _, v := range c._cookies {
		request.AddCookie(v)
	}
	c._cookies = nil

	return request, nil
}

func (c *Client) BuildUrlByParams(baseUrl string, params map[string]string) (string, error) {
	urlValue, err := url.Parse(baseUrl)
	if err != nil {
		return "", err
	}

	value := url.Values{}
	for k, v := range params {
		value.Add(k, v)
	}
	urlValue.RawQuery = value.Encode()

	return urlValue.String(), nil
}
